package nrpc.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import javax.security.auth.callback.Callback;

import message.MessageBuilder;
import message.MessageBuilderSupplier;


public abstract class ServerThread extends Thread {
	
	private final Selector selector;
	private ExecutorService executor;
	private ServerSocketChannel channel;
	private volatile boolean dead = false;
	
	public ServerThread(int port, int backlog) throws IOException {
		this.channel = ServerSocketChannel.open();
		this.channel.socket().setReuseAddress(true);
		this.channel.bind(new InetSocketAddress(port), backlog);
		this.channel.configureBlocking(false);
		this.selector = Selector.open();
		this.executor = Executors.newFixedThreadPool(Math.min(backlog, 300));
		this.channel.register(selector, SelectionKey.OP_ACCEPT);
	}
	
	public InetSocketAddress getAddress(){
		try {
			return (InetSocketAddress) channel.getLocalAddress();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public void close() {
		dead = true;
		executor.shutdown();
		try {
			this.selector.wakeup();
			this.selector.close();
			this.channel.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	protected abstract void read(SocketChannel c, ByteBuffer buffer, BiConsumer<? super SocketChannel, ? super Object> callback) throws IOException;
	
	protected abstract void deal(SocketChannel c, Object obj);
	
	public boolean service() {
		try {
			selector.select();
			Set<SelectionKey> set = selector.selectedKeys();
			Iterator<SelectionKey> iterator = set.iterator();
			while(iterator.hasNext()) {
				SelectionKey x = iterator.next();
				iterator.remove();
				if(x.isAcceptable()) {
					SocketChannel c = ((ServerSocketChannel)x.channel()).accept();
//					try {
//						System.out.println(c.getRemoteAddress());
//					} catch (IOException e1) {
//						e1.printStackTrace();
//					}
					c.configureBlocking(false);
					c.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
				}
				else if(x.isReadable()) { 
					SocketChannel c = ((SocketChannel)x.channel());
					try {
						read(c, (ByteBuffer)x.attachment(), (channel, obj)->{
							executor.execute(()->this.deal(channel, obj));
						});
//						if(input(c, (ByteBuffer)x.attachment())) {
//							deal(c);
//						}
					} catch (SocketException e) {
						c.close();
					}
				}
//				else if(x.isWritable()) {
//					SocketChannel c = (SocketChannel)x.channel();
//					output(c, "over\n");
////					System.out.println("s:" + ++t);
//					
//					c.close();
//				}
			}
		} catch (ClosedSelectorException | ClosedChannelException e){
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return true;
		}
		return false;
	}
	
	@Override
	public void run() {
		while(!dead) {
			if(service()) break;
		}
//		this.close();
	}
	
}
